package crontab

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"quant/backend/api/dto"
	"quant/backend/constants"
	"quant/backend/qmt"
	"quant/backend/utils"

	"github.com/duke-git/lancet/v2/mathutil"
)

// getStockCodeListBySector 获取沪深A股指定板块的所有股票代码列表
//
// 参数：
//
//	sector string - 需要查询的板块名称（需符合API要求的编码格式）
//
// 返回值：
//
//	[]string - 成功时返回股票代码字符串数组，格式为["SH.600000","SZ.300001"]等
//	error    - 失败时返回错误对象，包含具体的错误上下文信息
func getStockCodeListBySector(sector string) ([]string, error) {
	// 构建符合miniQMT服务规范的API请求URL
	baseURL := constants.MiniQMTBaseUrl + "/get_stock_list_in_sector"
	params := url.Values{"sector": []string{sector}}
	fullURL := baseURL + "?" + params.Encode()

	// 发送带自动重试机制的HTTP GET请求
	resp, err := utils.HttpGet(fullURL, nil, nil)
	if err != nil {
		return nil, fmt.Errorf("HttpGet failed for sector %s: %w", sector, err)
	}
	defer func(Body io.ReadCloser) {
		_ = Body.Close()
	}(resp.Body)

	// 校验HTTP响应状态码（mys）
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("unexpected status code %d for sector %s", resp.StatusCode, sector)
	}

	// 读取完整的HTTP响应体内容
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("io.ReadAll failed for sector %s: %w", sector, err)
	}

	// 解析JSON格式的股票代码列表（预期为字符串数组格式）
	var codeList []string
	if err := json.Unmarshal(body, &codeList); err != nil {
		return nil, fmt.Errorf("json.Unmarshal failed for sector %s: %w", sector, err)
	}

	return codeList, nil
}

// getDailyMarketData 获取当日行情数据
// 参数：
// strTime: 日期，格式为 YYYYMMDD
// strBody: 请求体，格式为 string, 股票代码列表，多个股票用英文逗号隔开
// 返回值：
func getDailyMarketData(strTime, strBody string) (map[string]string, error) {
	var (
		resp *http.Response
		body []byte
		err  error
	)

	// 发送 HTTP 请求获取市场数据
	resp, err = utils.HttpPost(constants.MiniQMTBaseUrl+"/get_market_data/"+strTime, strBody, nil, nil)
	if err != nil {
		return nil, fmt.Errorf("getDailyMarketData(%s,[body]) -> HttpPost failed: %w", strTime, err)
	}
	defer func(Body io.ReadCloser) {
		_ = Body.Close()
	}(resp.Body) // 必须关闭响应体

	// 检查 HTTP 状态码是否为成功状态
	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		return nil, fmt.Errorf("getDailyMarketData(%s,[body]) -> unexpected status code: %d", strTime, resp.StatusCode)
	}

	// 读取响应体
	body, err = io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("getDailyMarketData(%s,[body]) -> io.ReadAll failed: %w", strTime, err)
	}

	// 解析 JSON 数据
	m := make(map[string]string)
	if err = json.Unmarshal(body, &m); err != nil {
		return nil, fmt.Errorf("getDailyMarketData(%s,[body]) -> json.Unmarshal failed: %w", strTime, err)
	}

	return m, nil
}

// GenerateDailyModelList 将股票代码与对应日线数据映射转换为结构化的日线模型列表
//
// 参数:
//
//	m map[string]string - 股票代码到日线JSON数据的映射，key为股票代码，value为JSON格式字符串数据
//
// 返回值:
//
//	[]model.DailyModel - 结构化后的日线模型集合，包含各时间点的股票指标数据
//	error              - 处理过程中遇到的错误，包含空输入或JSON解析失败等场景的错误信息
//
// 处理流程:
//  1. 前置校验输入映射的有效性
//  2. 遍历处理每个股票代码的JSON数据
//  3. 解析JSON到中间传输对象(DailyDto)
//  4. 转换传输对象为领域模型(DailyModel)并进行数据格式化
func GenerateDailyModelList(m map[string]string) ([]qmt.DailyModel, error) {
	var dailyModelList []qmt.DailyModel

	// 检查输入映射有效性，避免处理空数据
	if m == nil || len(m) == 0 {
		return dailyModelList, fmt.Errorf("GenerateDailyModelList: m == nil || len(m) == 0")
	}

	// 遍历处理每个股票代码对应的数据
	for code, data := range m {
		var dailyDto dto.DailyDto
		// 解析JSON数据到DTO对象，失败时返回包含股票代码的错误信息
		if err := json.Unmarshal([]byte(data), &dailyDto); err != nil {
			return dailyModelList, fmt.Errorf("GenerateDailyModelList: json.Unmarshal error for code %s: %w", code, err)
		}

		// 跳过无时间序列的数据项（根据业务需求可调整为错误处理）
		if dailyDto.Time == nil {
			continue
		}

		// 将每个时间点的数据转换为标准模型，并进行数值精度处理
		for k := range dailyDto.Time {
			dailyModel := qmt.DailyModel{
				StockCode:   code,
				Time:        k,
				Open:        mathutil.RoundToFloat(dailyDto.Open[k], constants.Precision2),
				High:        mathutil.RoundToFloat(dailyDto.High[k], constants.Precision2),
				Low:         mathutil.RoundToFloat(dailyDto.Low[k], constants.Precision2),
				Close:       mathutil.RoundToFloat(dailyDto.Close[k], constants.Precision2),
				Volume:      mathutil.RoundToFloat(dailyDto.Volume[k], constants.Precision2),
				Amount:      mathutil.RoundToFloat(dailyDto.Amount[k], constants.Precision2),
				PreClose:    mathutil.RoundToFloat(dailyDto.PreClose[k], constants.Precision2),
				SuspendFlag: dailyDto.SuspendFlag[k],
			}
			dailyModelList = append(dailyModelList, dailyModel)
		}
	}
	return dailyModelList, nil
}
